#include "Model/Package.hpp"
#include "Model/Requirement.hpp"
#include "Model/DuplicateNameError.hpp"
#include "Model/InvalidHierarchyOperationError.hpp"
#include "Model/InvalidNameError.hpp"
#include <boost/foreach.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/iterator/indirect_iterator.hpp>
#include <algorithm>
#include <cassert>

namespace Model
{

Package::Package( const NameString& name, const NameString& description)
	: pParent_( NULL)
	, name_( name)
	, pProject_( NULL)
	, description_( description)
{
	if( name_.empty())
	{
		throw InvalidNameError( name_);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

Package::~Package()
{
	BOOST_FOREACH( Package* pChildPackage, childPackages_)
	{
		delete pChildPackage;
	}

	BOOST_FOREACH( Package* pRemovedChildPackage, removedPackages_)
	{
		delete pRemovedChildPackage;
	}

	BOOST_FOREACH( Requirement* pCurRequirement, requirements_)
	{
		delete pCurRequirement;
	}

	BOOST_FOREACH( Requirement* pCurRequirement, removedRequirements_)
	{
		delete pCurRequirement;
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::setName( const NameString& newName )
{
	if( newName.empty())
	{
		throw InvalidNameError( newName);
	}

	if( NULL != pParent_)
	{
		pParent_->checkForDuplicateName( newName);
	}

	name_ = newName;
}

// ---------------------------------------------------------------------------------------------------------------------

Package* Package::addNewPackage( const NameString& packageName )
{
	this->checkForDuplicateName( packageName);

	Package* pNewPackage = new Package( packageName);
	childPackages_.push_back( pNewPackage);
	pNewPackage->pParent_ = this;
	pNewPackage->pProject_ = this->pProject_;
	return pNewPackage;
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::addNewPackage( Package* package )
{
	assert( this != package);
	PackageIterator iter = std::find( removedPackages_.begin(), removedPackages_.end(), package);
	if( iter != removedPackages_.end())
	{
		this->addRemovedChildPackage( iter);
	}
	else
	{
		if( this == package->parentPackage())
		{
			using namespace boost::lambda;

			assert( childPackages_.end() 
				!= std::find_if( childPackages_.begin(), childPackages_.end(), _1 == package));
			return;
		}

		this->checkForDuplicateName( package->name());
		this->checkValidMovement( package);

		// WARNING: Assuming no exception can occur. This should be atomic!! (I know! I'm optimistic)
		// BEGIN TRANSACTION ---
		if( NULL != package->parentPackage())
		{
			package->parentPackage()->childPackages_.remove( package);
		}
		childPackages_.push_back( package);
		package->pParent_ = this;
		package->pProject_ = pProject_;
		// END TRANSACTION -- 
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::removeChildPackage( Package* package)
{
	assert( this == package->parentPackage());
	// WARNING: Assuming no exception can occur. This should be atomic!! (I know! I'm optimistic)
	// BEGIN TRANSACTION ---
	package->pParent_ = NULL;
	childPackages_.remove( package);
	removedPackages_.push_back( package);
	// END TRANSACTION ---
}

// ---------------------------------------------------------------------------------------------------------------------

bool Package::childUsesName( const NameString& name ) const
{
	using namespace boost::lambda;
	ConstPackageIterator iter = std::find_if( 
		childPackages_.begin(), childPackages_.end(), 
		bind( &Package::name, _1) == name);
	return iter != childPackages_.end();
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::checkForDuplicateName( const NameString& name )
{
	// Check for duplicate name surprise
	if( this->childUsesName( name))
	{
		throw DuplicateNameError( name);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

bool Package::hasAncestor( Package* pPackage ) const
{
	for( Package* pParent = pParent_; pParent != NULL; pParent = pParent->pParent_)
	{
		if( pParent == pPackage)
		{
			return true;
		}
	}
	return false;
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::checkValidMovement( Package* childPackage )
{
	if( this->hasAncestor( childPackage))
	{
		throw InvalidHierarchyOperationError();
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::setDescription( const RichText& newDescription )
{
	description_ = newDescription;
}

// ---------------------------------------------------------------------------------------------------------------------

NameString Package::fullID() const
{
	NameString result;
	if( NULL != pParent_)
	{
		result = pParent_->fullID();
		result += RQ_STR( ".");
	}
	result += this->name();
	return result;
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::setProject( Project* pProject )
{
	assert( (NULL == pProject_) || (pProject_ != NULL && pProject == NULL));
	pProject_ = pProject;
	using namespace boost::lambda;
	BOOST_FOREACH( Package* pCurPackage, childPackages_)
	{
		pCurPackage->setProject( pProject);
	}

	BOOST_FOREACH( Package* pCurRemPackage, removedPackages_)
	{
		pCurRemPackage->setProject( pProject);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::addRemovedChildPackage( PackageIterator removedPackageIter )
{
	Package* pRemovedPackage = *removedPackageIter;
	this->checkForDuplicateName( pRemovedPackage->name());
	childPackages_.push_back( pRemovedPackage);
	removedPackages_.erase( removedPackageIter);
	pRemovedPackage->pParent_ = this;
}

// ---------------------------------------------------------------------------------------------------------------------

Package::RequirementIterator Package::requirementsBegin()
{
	return requirements_.begin();
}

// ---------------------------------------------------------------------------------------------------------------------

Package::ConstRequirementIterator Package::requirementsBegin() const
{
	return requirements_.begin();
}

// ---------------------------------------------------------------------------------------------------------------------

Package::RequirementIterator Package::requirementsEnd()
{
	return requirements_.end();
}

// ---------------------------------------------------------------------------------------------------------------------

Package::ConstRequirementIterator Package::requirementsEnd() const
{
	return requirements_.end();
}

// ---------------------------------------------------------------------------------------------------------------------

std::size_t Package::numberOfRequirements() const
{
	return requirements_.size();
}

// ---------------------------------------------------------------------------------------------------------------------

Requirement* Package::addRequirement( const NameString& name, const RichText& description )
{
	if( this->requirementUsesName( name))
	{
		throw DuplicateNameError( name);
	}
	std::auto_ptr< Requirement> pNewRequirement( new Requirement( name, description));
	this->safeInsertRootRequirement( pNewRequirement.get());
	return pNewRequirement.release();
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::addRequirement( Requirement* pRequirement )
{
	assert( !pRequirement->hasParent());
	assert( NULL == pRequirement->package());
	assert( requirements_.end() == std::find( requirements_.begin(), requirements_.end(), pRequirement));
	if( this->isRemovedRootRequirement( pRequirement))
	{
		this->addRemovedRootRequirement( pRequirement);
	}
	else
	{
		if( this->requirementUsesName( pRequirement->name()))
		{
			throw DuplicateNameError( pRequirement->name());
		}
		this->safeInsertRootRequirement( pRequirement);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::addRemovedRootRequirement( Requirement* pRequirement)
{
	assert( this->isRemovedRootRequirement( pRequirement));
	if( this->requirementUsesName( pRequirement->name()))
	{
		throw DuplicateNameError( pRequirement->name());
	}
	removedRequirements_.remove( pRequirement);
	this->safeInsertRootRequirement( pRequirement);
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::safeInsertRootRequirement( Requirement* pRequirement)
{
	assert( !pRequirement->hasParent());
	assert( !this->isRemovedRootRequirement( pRequirement));
	assert( requirements_.end() == std::find( requirements_.begin(), requirements_.end(), pRequirement));
	assert( !this->requirementUsesName( pRequirement->name()));
	pRequirement->moveToPackage( this);
	requirements_.push_back( pRequirement);
}

// ---------------------------------------------------------------------------------------------------------------------

bool Package::isRemovedRootRequirement( Requirement* pRequirement) const
{
	return (removedRequirements_.end() 
		!= std::find( removedRequirements_.begin(), removedRequirements_.end(), pRequirement));
}

// ---------------------------------------------------------------------------------------------------------------------

bool Package::requirementUsesName( const NameString& nameToCheck ) const
{
	using namespace boost::lambda;
	ConstRequirementIterator iter = std::find_if( 
		requirements_.begin(), requirements_.end(), 
		bind( &Requirement::name, _1) == nameToCheck);
	return iter != requirements_.end();
}

// ---------------------------------------------------------------------------------------------------------------------

void Package::removeRequirement( Requirement* pRootRequirement )
{
	assert( this == pRootRequirement->package());
	assert( requirements_.end() != std::find( requirements_.begin(), requirements_.end(), pRootRequirement));
	requirements_.remove( pRootRequirement);
	pRootRequirement->moveToPackage( NULL);
	removedRequirements_.push_back( pRootRequirement);
}

} // namespace RqModel
